
import csv
import io 
import re

from dataclasses import dataclass, field

import src.codeql_tools.config as config
import src.codeql_tools.const as const

from .codeql_metadata import *
#DropValue=["None","True","False"]
#UnuseScopeNames=["genexpr","listcomp","dictcomp"]
AbsolutePathMapping={}
AbsolutePathMappingReverse={}

def get_absolute_path_mapping(absolute_path):
    absolute_path=absolute_path[len(config.codeql_prefix):]
    if absolute_path not in AbsolutePathMapping:
            AbsolutePathMapping[absolute_path]=len(AbsolutePathMapping)
            AbsolutePathMappingReverse[AbsolutePathMapping[absolute_path]]=absolute_path
    return AbsolutePathMapping[absolute_path]


@dataclass
class Namespace(VariableLocation):

    @property
    def absolute_path(self):
        return f'{config.codeql_prefix}{AbsolutePathMappingReverse.get(self._absolute_path,"")}'
    
@dataclass
class Variable(Namespace):
    # absolute_path:str=""
    #module_name:str=""
    # scope_names:list=field(default_factory=list)
    name:str=""
    var_type:int=0
    
    @property
    def is_const(self):
        if self.name==const.strconst:
            return True
        elif self.name==const.numconst:
            return True
        elif self.name in const.boolconst:
            return True
        elif self.name== const.nullconst:
            return True

        return False
    
    @property
    def global_id(self):
        return Variable.construct_global_id(self.absolute_path,self.scope_names,self.name)

    @property
    def owner_id(self):
        return Variable.construct_owner_id(self.absolute_path,self.scope_names)

    @staticmethod
    def construct_global_id(absolute_path,scopeNames,varName):
        if isinstance(absolute_path,str):
            absolute_path=get_absolute_path_mapping(absolute_path)
            
        if len(scopeNames)>1:    
            buffer=io.StringIO()
            for e in scopeNames[1:]:
                buffer.write('.'+e)
            return '{}[{}.{}]'.format(absolute_path,buffer.getvalue(),varName)
        else:
            return '{}[.{}]'.format(absolute_path,varName)
        
    def __hash__(self) -> int:
        return hash(self.global_id)
    
    @staticmethod
    def construct_owner_id(absolute_path,scopeNames):
        #absolute_path=get_absolute_path_mapping(absolute_path)
        
        if len(scopeNames)>1:    
            new_scope_names=[e for e in scopeNames if e not in config.exclude_function_name]
            buffer=io.StringIO()
            for e in new_scope_names[1:]:
                buffer.write('.'+e)
            return '{}[{}]'.format(absolute_path,buffer.getvalue())
        else:
            return '{}[.]'.format(absolute_path)
 
    @staticmethod
    def construct_row_id(absolute_path,row_number):
        #absolute_path=get_absolute_path_mapping(absolute_path)
        
        return f"{absolute_path}.{row_number}"

        
    
@dataclass
class FunctionDef(Variable):
    location:int=0
    column:int=0
    
    def get_function_owner_id(self):
        new_scope_names=[]
        new_scope_names+=self.scope_names
        new_scope_names.append(self.name)
        return Variable.construct_owner_id(self.absolute_path,new_scope_names)

    @property
    def function_name(self):
        def extract_protect(function_name:str):
                prefix="__" 
                pattern="__(.+?)__[0-9]*"
                
                prefix_check_out = function_name.startswith(prefix)
                
                unique_name_pattern="__{}__"
                
                if prefix_check_out:
                    keywords=re.findall(pattern,function_name)
                    if len(keywords)>0:
                        return unique_name_pattern.format(keywords[0])
                
                return None

        def extract_common(function_name:str):
            pattern="(.+?)__[0-9]+"
            common_word=["create","run","get","update","render","delete","save","main","setUp"]
            
            keyword=re.findall(pattern,function_name)
            if len(keyword)>0:
                keyword=keyword[0]
            else:
                return None

            if keyword in common_word:
                return keyword

            return None

        def extract_keyword(function_name:str):
            result = extract_common(function_name)
            
            if result is None:
                result=extract_protect(function_name)

            if result is None:
                return function_name

            return result

        return extract_keyword(self.name)
    
@dataclass
class FunctionParameter(Namespace):
    # absolute_path:str=""
    # scope_names:list=field(default_factory=list)
    # scope_types:list=field(default_factory=list)
    name:str=""
    function_name:str=""

    @property
    def owner_id(self):
        new_scope_names=[]
        new_scope_names+=self.scope_names
        new_scope_names.append(self.function_name)
        return Variable.construct_owner_id(self.absolute_path,new_scope_names)
    
def read_variable_from_csv(path):
    def safe_extract_args(target,indexes):
        for index in indexes:
            if index>=len(target):
                yield target[-1]
            else:
                yield target[index]
                
    def extract_scope(target):
        scopes=target.split(',')
        scopes=[e.split() for e in scopes]
        
        scope_types=[ScopeType.getScopeType(list(safe_extract_args(e,[0]))[0]) for e in scopes]
        scope_names=[list(safe_extract_args(e,[1]))[0] for e in scopes]
        
        return scope_types,scope_names
    
    result=[]
    with open(path,'r') as f:
        reader=csv.DictReader(f)
        columns=reader.fieldnames

        for row in reader:
            absolute_path=get_absolute_path_mapping(row[columns[0]])
            module_name,=safe_extract_args(row[columns[1]].split(),[1])
            scope_types,scope_names=extract_scope(row[columns[2]])
            var_type=safe_extract_args(row[columns[4]].split(),[0])
            name=row[columns[5]]
            if name not in config.exclude_variable_name:
                var=Variable(
                    absolute_path,
                    scope_names,
                    scope_types,
                    name,                
                    VarType.getVarType(var_type))
                result.append(var)        
    return result

def read_function_from_csv(path):
    def safe_extract_args(target,indexes):
        for index in indexes:
            if index>=len(target):
                yield target[-1]
            else:
                yield target[index]
                
    def extract_scope(target):
        scopes=target.split(',')
        scopes=[e.split() for e in scopes]
        
        scope_types=[ScopeType.getScopeType(list(safe_extract_args(e,[0]))[0]) for e in scopes]
        scope_names=[list(safe_extract_args(e,[1]))[0] for e in scopes]
        
        return scope_types,scope_names
    
    result=[]
    with open(path,'r') as f:
        reader=csv.DictReader(f)
        columns=reader.fieldnames

        for row in reader:
            absolute_path=get_absolute_path_mapping(row[columns[0]])
            module_name,=safe_extract_args(row[columns[1]].split(),[1])
            scope_types,scope_names=extract_scope(row[columns[2]])
            name,=safe_extract_args(row[columns[3]].split(),[1])
            row,column=safe_extract_args(row[columns[4]].split(':'),[-1,-2])
            var_type=VarType.Local if len(scope_names)>1 else VarType.Global
            
            if name in config.exclude_function_name:
                continue
            
            func=FunctionDef(
                absolute_path,
                scope_names,
                scope_types,
                name,
                var_type,
                int(column),
                int(row))
            result.append(func)        
    return result

def read_function_args_from_csv(path):
    def safe_extract_args(target,indexes):
        for index in indexes:
            if index>=len(target):
                yield target[-1]
            else:
                yield target[index]
                
    def extract_scope(target):
        scopes=target.split(',')
        scopes=[e.split() for e in scopes]
        
        scope_types=[ScopeType.getScopeType(list(safe_extract_args(e,[0]))[0]) for e in scopes]
        scope_names=[list(safe_extract_args(e,[1]))[0] for e in scopes]
        
        return scope_types,scope_names
    
    result=[]
    with open(path,'r') as f:
        reader=csv.DictReader(f)
        columns=reader.fieldnames

        for row in reader:
            absolute_path=get_absolute_path_mapping(row[columns[0]])
            function_name=row[columns[2]]
            scope_types,scope_names=extract_scope(row[columns[1]])
            name,=safe_extract_args(row[columns[3]].split(),[1])
                
            if name in const.empty_str:
                continue    
            
            if function_name in config.exclude_function_name:
                continue
                
            func=FunctionParameter(
                absolute_path,              
                scope_names,
                scope_types,
                name,
                function_name
            )
            result.append(func)        
    return result

def build_variable_table(var_seq,result={}):
    counter=0
    for var in var_seq:
        global_id=var.global_id
        if global_id not in result:
            result[global_id]=var
            counter+=1
    print("Added {} variable id from {} variable".format(counter,len(var_seq)))
    return result

def build_function_table(func_seq,result={}):
    counter=0
    for func in func_seq:
        global_id=func.global_id
        if global_id not in result:
            result[global_id]=func
            counter+=1
    print("Added {} function id from {} function".format(counter,len(func_seq)))
    return result

def build_function_args_table(function_args_seq,result={}):
    for func_param in function_args_seq:
        owner_id=func_param.owner_id
        if owner_id not in result:
            result[owner_id]=[]
        
        result[owner_id].append(func_param)    
    print("Added {} function parameter for {} function".format(len(function_args_seq),len(result)))
    return result

if __name__=="__main__":
    # table=build_variable_table(read_variable_from_csv("var.csv"))
    func_table=build_function_table(read_function_from_csv("result/py150k01/func.csv"))
    func_args_table=build_function_args_table(read_function_args_from_csv("result/py150k01/func-args.csv"))
    
    total=0
    exists=0
    
    
    for e in func_table:
        function_owner_id=func_table[e].get_function_owner_id() 
        total+=1
        if func_table[e].absolute_path=='D:/Code/SortByIDE/VSCode/CodeComplete/data/py150ksource/train/1/2/batch_96.py':
            print(function_owner_id)
        #print(function_owner_id)
        if function_owner_id in func_args_table:
            exists+=1
    
    print('----------------------------------')
    
    for e in func_args_table:
        if func_args_table[e][0].absolute_path=='D:/Code/SortByIDE/VSCode/CodeComplete/data/py150ksource/train/1/2/batch_96.py':
            print(e)
            
    print('{}/{}'.format(total,exists))